package com.winning.pmph.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.winning.pmph.entity.*;
import com.winning.pmph.entity.ExerciseEntry;
import com.winning.pmph.mapper.*;
import com.winning.pmph.utils.*;
import com.winning.pmph.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@Transactional
public class ExerciseBookService extends ServiceImpl<ExerciseBookMapper, ExerciseBook> {
    @Resource
    private BookCatalogItemService bookCatalogItemService;
    @Resource
    private BookArticleService bookArticleService;
    @Resource
    private ExerciseGroupService exerciseGroupService;
    @Resource
    private ExerciseEntryService exerciseEntryService;

    /**
     * 导入图书
     */
    public void importBook(ExerciseBook exerciseBook) {
        // 解压图书;
        String unzipPath = ParseBookZipUtil.unzipBook(exerciseBook.getFilePath(), null);
        // 递归查找某一文件夹下同时含有meta.xml，book.xml，book_catalog.xml，pics 4个文件的文件夹，这个文件夹下的
        // 文件才是正确的应该被解析的文件
        unzipPath = BookUtil.recursiveFindFile(new File(unzipPath));
        exerciseBook.setUnzipPath(unzipPath);
        // 解析图书元数据;
        parseMetadata(exerciseBook);
        // 解析图书front, back, 文章;
        parseArticle(exerciseBook);
        // 解析body.xml;
        parseBodyXml(exerciseBook);
        // 解析目录;
        parseCatalog(exerciseBook);
        // 设置关联关系;
        setForeignKey(exerciseBook);
        // 移动图片文件夹;
        movePics(exerciseBook);
        // 保存图书,文章(题库),目录;
        saveExerciseBook(exerciseBook);
    }

    private void movePics(ExerciseBook exerciseBook) {
        try {
            exerciseBook.setPictureSet(Arrays.stream(new File(exerciseBook.getUnzipPath() + "/pics").listFiles()).map(File::getName).collect(Collectors.toSet()));
            File srcDir = new File(exerciseBook.getUnzipPath());
            File destDir = new File(ApplicationContextUtil.BASE_DOWN_FORDER + "image/book/" + exerciseBook.getIsbn());
            if (destDir.exists()) {
                FileUtils.deleteDirectory(destDir);
            }
            FileUtils.moveDirectory(srcDir, destDir);
            setPicsUrl(exerciseBook);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void saveExerciseBook(ExerciseBook exerciseBook) {
        // 保存目录;
        bookCatalogItemService.saveBatch(exerciseBook.getFrontCatalogItemList(), Const.BATCH_SIZE);
        bookCatalogItemService.saveBatch(exerciseBook.getBodyCatalogItemList(), Const.BATCH_SIZE);
        bookCatalogItemService.saveBatch(exerciseBook.getBackCatalogItemList(), Const.BATCH_SIZE);
        // 保存文章;
        bookArticleService.saveBatch(exerciseBook.getFrontArticleList(), Const.BATCH_SIZE);
        bookArticleService.saveBatch(exerciseBook.getBackArticleList(), Const.BATCH_SIZE);
        // 保存exerciseGroup;
        exerciseGroupService.saveBatch(exerciseBook.getExerciseGroupList(), Const.BATCH_SIZE);
        // 保存exerciseEntry;
        exerciseEntryService.saveBatch(exerciseBook.getExerciseEntryList(), Const.BATCH_SIZE);
        // 保存图书;
        exerciseBook.setImportStatus(Journal.STATUS_IMPORT_SUCCESS);
        exerciseBook.setFailureReason(null);
        exerciseBook.setImportDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
        saveOrUpdate(exerciseBook);
    }

    private void setForeignKey(ExerciseBook exerciseBook) {
        // front; 文章的图书id, 目录的图书id, 目录的文章id, 目录的父目录;
        if (CollectionUtils.isNotEmpty(exerciseBook.getFrontArticleList())) {
            // sequenceNumber -> id;
            Map<String, String> frontArticleSequenceNumber2Id = exerciseBook.getFrontArticleList().stream()
                    .peek(frontArticle -> frontArticle.setBookId(exerciseBook.getId()))
                    .collect(Collectors.toMap(BookArticle::getSequenceNumber, BookArticle::getId));
            Map<String, String> frontCatalogItemSequenceNumber2Id = exerciseBook.getFrontCatalogItemList().stream()
                    .peek(frontArticle -> frontArticle.setBookId(exerciseBook.getId()))
                    .collect(Collectors.toMap(BookCatalogItem::getSequenceNumber, BookCatalogItem::getId));
            exerciseBook.getFrontCatalogItemList().forEach(catalogItem -> {
                String sequenceNumber = catalogItem.getSequenceNumber();
                while (sequenceNumber.contains("-") && !frontArticleSequenceNumber2Id.containsKey(sequenceNumber)) {
                    sequenceNumber = sequenceNumber.substring(0, sequenceNumber.lastIndexOf("-"));
                }
                catalogItem.setBookArticleId(frontArticleSequenceNumber2Id.get(sequenceNumber));
                if (catalogItem.getLayer() > 1) {
                    String parentSequenceNumber = catalogItem.getSequenceNumber().substring(0, catalogItem.getSequenceNumber().lastIndexOf("-"));
                    catalogItem.setParentId(frontCatalogItemSequenceNumber2Id.get(parentSequenceNumber));
                }
            });
        }

        // body;
        // 准备目录sequenceNumber2Id;
        Map<String, String> bodyCatalogItemSequenceNumber2Id = exerciseBook.getBodyCatalogItemList().stream()
                .peek(bodyArticle -> bodyArticle.setBookId(exerciseBook.getId()))
                .collect(Collectors.toMap(BookCatalogItem::getSequenceNumber, BookCatalogItem::getId));
        // group 绑定图书id;
        exerciseBook.getExerciseGroupList().forEach(bodyArticle -> bodyArticle.setExerciseBookId(exerciseBook.getId()));
        // exerciseentry 绑定图书id, 目录id;
        exerciseBook.getExerciseEntryList().forEach(exerciseEntry -> {
                    exerciseEntry.setExerciseBookId(exerciseBook.getId());
                    exerciseEntry.setExerciseCatalogId(bodyCatalogItemSequenceNumber2Id.get(exerciseEntry.getSequenceNumber()));
                });
        // 目录的图书&文章id(第一个exerciseentry), 目录的父目录;
        exerciseBook.getBodyCatalogItemList().forEach(catalogItem -> {
            // 第一个exerciseentry id;
            Optional<ExerciseEntry> exerciseEntryOptional = exerciseBook.getExerciseEntryList().stream()
                    .filter(exerciseEntry -> exerciseEntry.getSequenceNumber().startsWith(catalogItem.getSequenceNumber()))
                    .findFirst();
            if (exerciseEntryOptional.isPresent()) {
                catalogItem.setBookArticleId(exerciseEntryOptional.get().getId());
            }
            if (catalogItem.getLayer() > 1) {
                String parentSequenceNumber = catalogItem.getSequenceNumber().substring(0, catalogItem.getSequenceNumber().lastIndexOf("-"));
                catalogItem.setParentId(bodyCatalogItemSequenceNumber2Id.get(parentSequenceNumber));
            }
        });

        // back; 文章的图书id, 目录的图书&文章id, 目录的父目录;
        if (CollectionUtils.isNotEmpty(exerciseBook.getBackArticleList())) {
            // sequenceNumber -> id;
            Map<String, String> backArticleSequenceNumber2Id = exerciseBook.getBackArticleList().stream()
                    .peek(backArticle -> backArticle.setBookId(exerciseBook.getId()))
                    .collect(Collectors.toMap(BookArticle::getSequenceNumber, BookArticle::getId));
            Map<String, String> backCatalogItemSequenceNumber2Id = exerciseBook.getBackCatalogItemList().stream()
                    .peek(backArticle -> backArticle.setBookId(exerciseBook.getId()))
                    .collect(Collectors.toMap(BookCatalogItem::getSequenceNumber, BookCatalogItem::getId));
            exerciseBook.getBackCatalogItemList().forEach(catalogItem -> {
                String sequenceNumber = catalogItem.getSequenceNumber();
                while (sequenceNumber.contains("-") && !backArticleSequenceNumber2Id.containsKey(sequenceNumber)) {
                    sequenceNumber = sequenceNumber.substring(0, sequenceNumber.lastIndexOf("-"));
                }
                catalogItem.setBookArticleId(backArticleSequenceNumber2Id.get(sequenceNumber));
                if (catalogItem.getLayer() > 1) {
                    String parentSequenceNumber = catalogItem.getSequenceNumber().substring(0, catalogItem.getSequenceNumber().lastIndexOf("-"));
                    catalogItem.setParentId(backCatalogItemSequenceNumber2Id.get(parentSequenceNumber));
                }
            });
        }
    }

    private void parseCatalog(ExerciseBook exerciseBook) {
        try {
            File frontFile = new File(exerciseBook.getUnzipPath() + "/front_catalog.xml");
            if (frontFile.exists()) {
                JAXBContext frontJaxbContent = JAXBContext.newInstance(FrontOfCatalogItemWrapper.class);
                Unmarshaller frontJaxbContentUnmarshaller = frontJaxbContent.createUnmarshaller();
                FileInputStream frontFileFIS = new FileInputStream(frontFile);
                FrontOfCatalogItemWrapper frontOfCatalogItemWrapper = (FrontOfCatalogItemWrapper) frontJaxbContentUnmarshaller.unmarshal(frontFileFIS);
                frontOfCatalogItemWrapper.getFrontCatalogItemList().forEach(item -> {
                    item.setSituation("FRONT");
                    item.setSequenceNumber(item.getId());
                    item.setId(IdWorker.getIdStr());
                });
                exerciseBook.setFrontCatalogItemList(frontOfCatalogItemWrapper.getFrontCatalogItemList());
            }

            File bodyFile = new File(exerciseBook.getUnzipPath() + "/body_catalog.xml");
            if (bodyFile.exists()) {
                JAXBContext bodyJaxbContext = JAXBContext.newInstance(BodyOfCatalogItemWrapper.class);
                Unmarshaller bodyJaxbContextUnmarshaller = bodyJaxbContext.createUnmarshaller();
                FileInputStream bodyFileFIS = new FileInputStream(bodyFile);
                BodyOfCatalogItemWrapper bodyOfCatalogItemWrapper = (BodyOfCatalogItemWrapper) bodyJaxbContextUnmarshaller.unmarshal(bodyFileFIS);
                bodyOfCatalogItemWrapper.getBodyCatalogItemList().forEach(item -> {
                    item.setSituation("BODY");
                    item.setSequenceNumber(item.getId());
                    item.setId(IdWorker.getIdStr());
                });
                exerciseBook.setBodyCatalogItemList(bodyOfCatalogItemWrapper.getBodyCatalogItemList());
            }

            File backFile = new File(exerciseBook.getUnzipPath() + "/back_catalog.xml");
            if (backFile.exists()) {
                JAXBContext backJaxbContext = JAXBContext.newInstance(BackOfCatalogItemWrapper.class);
                Unmarshaller backJaxbContextUnmarshaller = backJaxbContext.createUnmarshaller();
                FileInputStream backFileFIS = new FileInputStream(backFile);
                BackOfCatalogItemWrapper backOfCatalogItemWrapper = (BackOfCatalogItemWrapper) backJaxbContextUnmarshaller.unmarshal(backFileFIS);
                backOfCatalogItemWrapper.getBackCatalogItemList().forEach(item -> {
                    item.setSituation("BACK");
                    item.setSequenceNumber(item.getId());
                    item.setId(IdWorker.getIdStr());
                });
                exerciseBook.setBackCatalogItemList(backOfCatalogItemWrapper.getBackCatalogItemList());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void parseBodyXml(ExerciseBook exerciseBook) {
        try {
            File bodyFile = new File(exerciseBook.getUnzipPath() + "/body.xml");
            if (bodyFile.exists()) {
                Element body = Jsoup.parse(bodyFile, "utf8").body();
                // 保持先后顺序, group, entry, 因为后续节点需要用到前面解析设置的id;
                // 把A1/A2题型 外层套question group;
                Elements children = body.child(0).children();
                for (int i = 0; i < children.size(); i++) {
                    Element crtElement = children.get(i);
                    if (StringUtils.equals(ExerciseGroup.ROLE_A1, crtElement.attr("role"))) {
                        Element group = new Element("question");
                        group.appendChild(crtElement);
                        body.child(0).insertChildren(i, group);
                    }
                }
                // question group;
                List<Element> questionGroupElementList = body.child(0).children().stream().filter(element -> StringUtils.equals("question", element.tagName())).collect(Collectors.toList());
                exerciseBook.setExerciseGroupList(new ArrayList<>(questionGroupElementList.size()));
                questionGroupElementList.forEach(questionGroup -> {
                    // 属性在第一个 exerciseentry 标签内;
                    ExerciseGroup exerciseGroup = new ExerciseGroup();
                    exerciseGroup.setId(IdWorker.getIdStr());
                    exerciseGroup.setRole(questionGroup.child(0).attr("role"));
                    questionGroup.getElementsByTag("total").forEach(total -> {
                        if (total.hasAttr("serial") && total.hasAttr("number")) {
                            exerciseGroup.setTotalSerialNumber(total.html());
                        }
                        else if (total.hasAttr("title") && total.hasAttr("stem")) {
                            exerciseGroup.setTotalTitleStem(total.html());
                        }
                    });
                    if (questionGroup.getElementsByTag("case").size() > 0) {
                        exerciseGroup.setCaseAlias(questionGroup.getElementsByTag("case").first().html());
                    } else {
                        String caseAlias = questionGroup.children().stream()
                                .map(element -> element.getElementsByTag("question"))
                                .filter(CollectionUtils::isNotEmpty)
                                .map(element -> element.first().child(0).html())
                                .collect(Collectors.joining(System.lineSeparator()));
                        exerciseGroup.setCaseAlias(caseAlias);
                    }
                    if (questionGroup.getElementsByTag("classify").size() > 0) {
                        exerciseGroup.setClassify(questionGroup.getElementsByTag("classify").first().html());
                    }
                    exerciseBook.getExerciseGroupList().add(exerciseGroup);
                    questionGroup.attr("id", exerciseGroup.getId());
                });
                // entry;
                List<Element> exerciseEntryElements = body.getElementsByTag("exerciseentry").stream()
                        .filter(element -> element.getElementsByTag("nonum").size() > 0)
                        .collect(Collectors.toList());
                exerciseBook.setExerciseEntryList(new ArrayList<>(exerciseEntryElements.size()));
                exerciseEntryElements.forEach(entry -> {
                    ExerciseEntry exerciseEntry = new ExerciseEntry();
                    exerciseEntry.setId(IdWorker.getIdStr());
                    exerciseEntry.setRole(entry.attr("role"));
                    exerciseEntry.setSequenceNumber(entry.getElementsByTag("catalog").first().html());
                    exerciseEntry.setNonum(entry.getElementsByTag("nonum").first().html());
                    exerciseEntry.setQuestion(entry.getElementsByTag("question").first().child(0).html());

                    if (StringUtils.equals("question", entry.parent().tagName())) {
                        exerciseEntry.setGroupId(entry.parent().attr("id"));
                    }

                    Elements childChoice = entry.getElementsByTag("choice");
                    if (CollectionUtils.isEmpty(childChoice)) {
                        childChoice = entry.parent().child(0).getElementsByTag("choice");
                    }
                    JSONArray choiceArray = new JSONArray();
                    childChoice.first().getElementsByTag("para").stream()
                            .map(element -> element.html())
                            .forEach(choiceArray::add);
                    exerciseEntry.setChoiceArray(choiceArray);

                    exerciseEntry.setAnswer(entry.getElementsByTag("answer").first().text());
                    Elements answerAnalyses = entry.getElementsByTag("answeranalyses");
                    if (answerAnalyses.size() > 0) {
                        exerciseEntry.setAnswerAnalyses(answerAnalyses.first().html());
                    }
                    exerciseEntry.setClassify(entry.getElementsByTag("classify").first().html());
                    exerciseBook.getExerciseEntryList().add(exerciseEntry);
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void parseArticle(ExerciseBook exerciseBook) {
        try {
            File frontFile = new File(exerciseBook.getUnzipPath() + "/front.xml");
            if (frontFile.exists()) {
                JAXBContext frontJaxbContent = JAXBContext.newInstance(FrontOfBookArticleWrapper.class);
                Unmarshaller frontJaxbContentUnmarshaller = frontJaxbContent.createUnmarshaller();
                FileInputStream frontFileFIS = new FileInputStream(frontFile);
                FrontOfBookArticleWrapper frontArticleWrapper = (FrontOfBookArticleWrapper) frontJaxbContentUnmarshaller.unmarshal(frontFileFIS);
                frontArticleWrapper.getFrontArticleList().forEach(frontArticle -> {
                    frontArticle.setId(IdWorker.getIdStr());
                    frontArticle.setSituation("FRONT");
                });
                exerciseBook.setFrontArticleList(frontArticleWrapper.getFrontArticleList());
            }

            File backFile = new File(exerciseBook.getUnzipPath() + "/back.xml");
            if (backFile.exists()) {
                JAXBContext backJaxbContext = JAXBContext.newInstance(BackOfBookArticleWrapper.class);
                Unmarshaller backJaxbContextUnmarshaller = backJaxbContext.createUnmarshaller();
                FileInputStream backFileFIS = new FileInputStream(backFile);
                BackOfBookArticleWrapper backArticleWrapper = (BackOfBookArticleWrapper) backJaxbContextUnmarshaller.unmarshal(backFileFIS);
                backArticleWrapper.getBackArticleList().forEach(backArticle -> {
                    backArticle.setId(IdWorker.getIdStr());
                    backArticle.setSituation("BACK");
                });
                exerciseBook.setBackArticleList(backArticleWrapper.getBackArticleList());
            }

            // 图书文章, 转dom;
            if (CollectionUtils.isNotEmpty(exerciseBook.getFrontArticleList())) {
                exerciseBook.getFrontArticleList().forEach(article -> article.setUnitContentDom(Jsoup.parse(article.getUnitContent())));
            }
            if (CollectionUtils.isNotEmpty(exerciseBook.getBackArticleList())) {
                exerciseBook.getBackArticleList().forEach(article -> article.setUnitContentDom(Jsoup.parse(article.getUnitContent())));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void parseMetadata(ExerciseBook exerciseBook) {
        JSONObject metadata = BookUtil.parseMetadataXml2Json(exerciseBook.getUnzipPath(), false);
        String isbn = metadata.getString("ISBN");
        String bookName = metadata.getJSONObject("Titles").getString("Main_Title");
        JSONArray category = metadata.getJSONArray("Category");
        exerciseBook.setMetadata(metadata);
        exerciseBook.setName(bookName);
        exerciseBook.setIsbn(isbn);
        exerciseBook.setCategory(category);
    }

    private void setPicsUrl(ExerciseBook exerciseBook) {
        // 图片url;
        String webPath = "/ymer/action/file/showImage?filename=image/book/" + exerciseBook.getIsbn() + "/pics/";
        // 修改前言图片路径;
        List<BookArticle> frontArticleList = exerciseBook.getFrontArticleList();
        if (CollectionUtils.isNotEmpty(frontArticleList)) {
            frontArticleList.stream().forEach(bookArticle -> {
                BookUtil.bindImgSrc(exerciseBook, bookArticle, webPath, BookArticle.SITUATION_FRONT);
            });
        }
        // 修改后记图片路径;
        List<BookArticle> backArticleList = exerciseBook.getBackArticleList();
        if (CollectionUtils.isNotEmpty(backArticleList)) {
            backArticleList.stream().forEach(bookArticle -> {
                BookUtil.bindImgSrc(exerciseBook, bookArticle, webPath, BookArticle.SITUATION_BACK);
            });
        }
    }

    public void deleteById(String id) {
        // 目录;
        bookCatalogItemService.remove(Wrappers.lambdaQuery(BookCatalogItem.class).eq(BookCatalogItem::getBookId, id));
        // 文章;
        bookArticleService.remove(Wrappers.lambdaQuery(BookArticle.class).eq(BookArticle::getBookId, id));
        // exerciseGroup;
        exerciseGroupService.remove(Wrappers.lambdaQuery(ExerciseGroup.class).eq(ExerciseGroup::getExerciseBookId, id));
        // 保存exerciseEntry;
        exerciseEntryService.remove(Wrappers.lambdaQuery(ExerciseEntry.class).eq(ExerciseEntry::getExerciseBookId, id));
        // 保存图书;
        removeById(id);
    }

    public Page<ExerciseGroup> selectExerciseGroupPage(ExerciseGroup exerciseGroup) {
        Page<ExerciseGroup> page = new Page<>(exerciseGroup.getCurrentPage(), exerciseGroup.getPageSize());
        LambdaQueryWrapper<ExerciseGroup> queryWrapper = Wrappers.lambdaQuery(ExerciseGroup.class)
                .eq(ExerciseGroup::getExerciseBookId, exerciseGroup.getExerciseBookId());
        if (StringUtils.isNotEmpty(exerciseGroup.getRole())) {
            queryWrapper.eq(ExerciseGroup::getRole, exerciseGroup.getRole());
        }
        if (StringUtils.isNotEmpty(exerciseGroup.getCaseAlias())) {
            queryWrapper.like(ExerciseGroup::getCaseAlias, exerciseGroup.getCaseAlias());
        }
        if (StringUtils.isNotEmpty(exerciseGroup.getClassify())) {
            queryWrapper.like(ExerciseGroup::getClassify, exerciseGroup.getClassify());
        }
        return exerciseGroupService.page(page, queryWrapper);
    }

    public void deleteExerciseGroup(ExerciseGroup exerciseGroup) {
        exerciseEntryService.remove(Wrappers.lambdaQuery(ExerciseEntry.class)
                .eq(ExerciseEntry::getGroupId, exerciseGroup.getId()));
        exerciseGroupService.removeById(exerciseGroup.getId());
    }

    public void releaseExerciseBook(ExerciseBook exerciseBook) {
        exerciseBook = getById(exerciseBook.getId());
        exerciseBook.setVersion(exerciseBook.getVersion() + 1);
        exerciseBook.setStatus(ExerciseBook.BOOK_STATUS_PUBLISHED);
        updateById(exerciseBook);
    }

    public void releaseExerciseBookList(List<String> exerciseBookIdList) {
        for (String id : exerciseBookIdList) {
            ExerciseBook exerciseBook = getById(id);
            exerciseBook.setVersion(exerciseBook.getVersion() + 1);
            exerciseBook.setStatus(ExerciseBook.BOOK_STATUS_PUBLISHED);
            updateById(exerciseBook);
        }
    }

}
